/*******************************************************************************
 * Copyright (c) 2006 CEA List.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     CEA List - initial API and implementation
 *******************************************************************************/
package com.cea.papyrus.tabbedproperties.composites;

import java.util.Iterator;
import java.util.List;

import org.eclipse.gef.EditPart;
import org.eclipse.gef.commands.Command;
import org.eclipse.gef.commands.CommandStack;
import org.eclipse.gef.commands.CompoundCommand;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.text.Assert;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.window.DefaultToolTip;
import org.eclipse.jface.window.ToolTip;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.ArmEvent;
import org.eclipse.swt.events.ArmListener;
import org.eclipse.swt.events.MenuEvent;
import org.eclipse.swt.events.MenuListener;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.MenuItem;
import org.eclipse.swt.widgets.TableItem;
import org.eclipse.ui.views.properties.tabbed.TabbedPropertySheetPage;
import org.eclipse.ui.views.properties.tabbed.TabbedPropertySheetWidgetFactory;
import org.eclipse.uml2.uml.AnyReceiveEvent;
import org.eclipse.uml2.uml.BehavioredClassifier;
import org.eclipse.uml2.uml.CallEvent;
import org.eclipse.uml2.uml.ChangeEvent;
import org.eclipse.uml2.uml.Event;
import org.eclipse.uml2.uml.LiteralString;
import org.eclipse.uml2.uml.Operation;
import org.eclipse.uml2.uml.Signal;
import org.eclipse.uml2.uml.SignalEvent;
import org.eclipse.uml2.uml.State;
import org.eclipse.uml2.uml.TimeEvent;
import org.eclipse.uml2.uml.Transition;
import org.eclipse.uml2.uml.Trigger;
import org.eclipse.uml2.uml.UMLFactory;

import com.cea.papyrus.core.PapyrusPlugin;
import com.cea.papyrus.core.commands.MoveInListCommand;
import com.cea.papyrus.core.commands.UMLElementCreateCommand;
import com.cea.papyrus.core.commands.factories.UMLDi2ElementDeleteFactory;
import com.cea.papyrus.core.editor.CommandStackUtils;
import com.cea.papyrus.core.editpart.IUMLElementEditPart;
import com.cea.papyrus.core.ui.dialogs.ActiveClassSignalTreeSelectionDialog;
import com.cea.papyrus.core.ui.dialogs.BehavioredClassifierOperationTreeSelectionDialog;
import com.cea.papyrus.core.ui.dialogs.PackageAnyReceiveEventTreeSelectionDialog;
import com.cea.papyrus.core.ui.dialogs.PackageChangeEventTreeSelectionDialog;
import com.cea.papyrus.core.ui.dialogs.PackageTimeEventTreeSelectionDialog;
import com.cea.papyrus.core.utils.Debug;
import com.cea.papyrus.core.utils.LookForElement;
import com.cea.papyrus.diagram.statemachine.editpart.TransitionEditPart;
import com.cea.papyrus.diagram.statemachine.editpart.TransitionGuardAndAllDisplayEditPart;
import com.cea.papyrus.diagram.statemachine.helper.EdgeTransitionHelper;
import com.cea.papyrus.diagram.statemachine.helper.NodeTriggerHelper;
import com.cea.papyrus.diagraminterchange2.di2.GraphNode;
import com.cea.papyrus.umlutils.Element;

/**
 * Composite that displays the triggers of a Transition or a State.
 * 
 * @author David Servat
 * @since 5 apr. 2007
 */
public class TriggersComposite extends DecoratedTableComposite{

	/**
	 * debug channel.
	 */
	protected static int channel = 501;

	/**
	 * The parent Composite.
	 */
	protected Composite parent;

	/**
	 * Default Constructor.
	 */
	public TriggersComposite(){
		super("Triggers");
	}

	/**
	 * Constructor with given name for label.
	 */
	public TriggersComposite(String name){
		super(name);
	}

	/**
	 * key to access UML Data from table items.
	 */ 
	protected final static String KEY = "triggers_data";

	/**
	 * key to access to AnyReceiveEvent image.
	 */
	protected final static String ANY_RECEIVE_EVENT_IMAGE = PapyrusPlugin.UML_ICONS_16x16+"AnyReceiveEvent.gif";

	/**
	 * key to access to CallEvent image.
	 */
	protected final static String CALL_EVENT_IMAGE = PapyrusPlugin.UML_ICONS_16x16+"CallEvent.gif";

	/**
	 * key to access to ChangeEvent image.
	 */
	protected final static String CHANGE_EVENT_IMAGE = PapyrusPlugin.UML_ICONS_16x16+"ChangeEvent.gif";

	/**
	 * key to access to SignalEvent image.
	 */
	protected final static String SIGNAL_EVENT_IMAGE = PapyrusPlugin.UML_ICONS_16x16+"SignalEvent.gif";

	/**
	 * key to access to Timevent image.
	 */
	protected final static String TIME_EVENT_IMAGE = PapyrusPlugin.UML_ICONS_16x16+"TimeEvent.gif";

	/**
	 * key to access to Trigger image.
	 */
	protected final static String TRIGGER_IMAGE = PapyrusPlugin.UML_ICONS_16x16+"Trigger.gif";

	/**
	 * The Menu to show all kinds of possible Event.
	 */
	protected Menu menu;

	/**
	 * Various menu items.
	 */
	protected MenuItem anyReceiveEventItem, callEventItem, changeEventItem, signalEventItem, timeEventItem;

	/**
	 * Associated selection listeners.
	 */
	protected SelectionListener anyReceiveEventItemListener, callEventItemListener, changeEventItemListener, signalEventItemListener, timeEventItemListener;

	/**
	 * Property page
	 */
	protected TabbedPropertySheetPage tabbedPropertySheetPage = null;

	/**
	 * Edit part
	 */
	protected IUMLElementEditPart editPart = null;

	/**
	 * Stores the tabbed property page before handling the call
	 * to createContent
	 */
	public Composite createContent(Composite parent, TabbedPropertySheetPage tabbedPropertySheetPage){
		this.tabbedPropertySheetPage = tabbedPropertySheetPage;
		return createContent(parent, tabbedPropertySheetPage.getWidgetFactory());
	}

	/**
	 * Enables to store the EditPart for refresh.
	 */
	public void setEditPart(IUMLElementEditPart editPart){
		this.editPart = editPart;
	}

	/**
	 * Creates contents.
	 * 
	 * @param parent Composite.
	 * @param factory TabbedPropertySheetWidgetFactory.
	 * 
	 * @return specific Composite.
	 */
	@Override
	public Composite createContent(Composite parent, TabbedPropertySheetWidgetFactory factory){
		Composite composite = super.createContent(parent, factory);

		//store parent composite
		this.parent = parent;

		//add an additional menu to the add Button
		createMenuForAddButton();

		return composite;
	}



	/**
	 * Adds an additional menu to the addButton. So user can choose which type of trigger to create.
	 */
	protected void createMenuForAddButton(){
		menu = new Menu (addButton);

		MenuTooltipListener tooltipListener = new MenuTooltipListener(parent.getShell());
		menu.addMenuListener(tooltipListener);

		// add anyReceiveEvent menu item
		anyReceiveEventItem = new MenuItem (menu, SWT.PUSH);
		anyReceiveEventItem.setText("AnyReceiveEvent");
		anyReceiveEventItem.setImage(PapyrusPlugin.getImage(ANY_RECEIVE_EVENT_IMAGE));
		anyReceiveEventItem.addSelectionListener(anyReceiveEventItemListener = new AddElementSelectionListener(AnyReceiveEvent.class));
		anyReceiveEventItem.addArmListener(tooltipListener);

		// add callEvent menu item
		callEventItem = new MenuItem (menu, SWT.PUSH);
		callEventItem.setText("CallEvent");
		callEventItem.setImage(PapyrusPlugin.getImage(CALL_EVENT_IMAGE));
		callEventItem.addSelectionListener(callEventItemListener = new AddElementSelectionListener(CallEvent.class));
		callEventItem.addArmListener(tooltipListener);

		// add changeEvent menu item
		changeEventItem = new MenuItem (menu, SWT.PUSH);
		changeEventItem.setText("ChangeEvent");
		changeEventItem.setImage(PapyrusPlugin.getImage(CHANGE_EVENT_IMAGE));
		changeEventItem.addSelectionListener(changeEventItemListener = new AddElementSelectionListener(ChangeEvent.class));
		changeEventItem.addArmListener(tooltipListener);


		// add signalEvent menu item
		signalEventItem = new MenuItem (menu, SWT.PUSH);
		signalEventItem.setText("SignalEvent");
		signalEventItem.setImage(PapyrusPlugin.getImage(SIGNAL_EVENT_IMAGE));
		signalEventItem.addSelectionListener(signalEventItemListener = new AddElementSelectionListener(SignalEvent.class));
		signalEventItem.addArmListener(tooltipListener);

		// add timeEvent menu item
		timeEventItem = new MenuItem (menu, SWT.PUSH);
		timeEventItem.setText("TimeEvent");
		timeEventItem.setImage(PapyrusPlugin.getImage(TIME_EVENT_IMAGE));
		timeEventItem.addSelectionListener(timeEventItemListener = new AddElementSelectionListener(TimeEvent.class));
		timeEventItem.addArmListener(tooltipListener);

		addButton.setMenu (menu);
	}

	/**
	 * Overriden to disable the signalEventItem from menu in case the BehavioredClassifier is not an active class.
	 * 
	 * @param element the com.cea.utils.Element
	 */
	@Override
	public void setElement(Element element){
		super.setElement(element);

		Assert.isNotNull(element.getUml2Element(), "Passed argument is null");
		Assert.isTrue(((element.getUml2Element() instanceof State) || (element.getUml2Element() instanceof Transition)), "Passed argument is not a Transition nor a State");

		BehavioredClassifier behavioredClassifier = null;
		if(element.getUml2Element() instanceof Transition)
			behavioredClassifier = (BehavioredClassifier)((Transition)element.getUml2Element()).containingStateMachine().getOwner();
		else if(element.getUml2Element() instanceof State)
			behavioredClassifier = (BehavioredClassifier)((State)element.getUml2Element()).containingStateMachine().getOwner();

		if((behavioredClassifier instanceof org.eclipse.uml2.uml.Class) && ((org.eclipse.uml2.uml.Class)behavioredClassifier).isActive())
			signalEventItem.setEnabled(true);
		else
			signalEventItem.setEnabled(false);
	}

	/**
	 * Refreshes the composite.
	 */
	@Override
	public void refresh(){
		Iterator it = null;

		Assert.isNotNull(element.getUml2Element(), "Passed argument is null");
		Assert.isTrue(((element.getUml2Element() instanceof State) || (element.getUml2Element() instanceof Transition)), "Passed argument is not a Transition nor a State");

		if(element.getUml2Element() instanceof Transition)
			it = ((Transition)element.getUml2Element()).getTriggers().iterator();
		else if(element.getUml2Element() instanceof State)
			it = ((State)element.getUml2Element()).getDeferrableTriggers().iterator();

		if(!table.isDisposed()){
			//remove parameters from table 
			table.removeAll();

			//read and add trigger
			while (it.hasNext()){
				Trigger trigger = (Trigger)it.next();
				Event event = trigger.getEvent();
				TableItem item = new TableItem(table, SWT.BORDER);
				item.setData(KEY, trigger);
				if(event != null){
					if(event instanceof AnyReceiveEvent){
						item.setImage(PapyrusPlugin.getImage(ANY_RECEIVE_EVENT_IMAGE));
					} 
					else if(event instanceof CallEvent){
						item.setImage(PapyrusPlugin.getImage(CALL_EVENT_IMAGE));
					} 
					else if(event instanceof ChangeEvent){
						item.setImage(PapyrusPlugin.getImage(CHANGE_EVENT_IMAGE));
					} 
					else if(event instanceof SignalEvent){
						item.setImage(PapyrusPlugin.getImage(SIGNAL_EVENT_IMAGE));
					} 
					else if(event instanceof TimeEvent){
						item.setImage(PapyrusPlugin.getImage(TIME_EVENT_IMAGE));
					} 
					else{
						item.setImage(PapyrusPlugin.getImage(""));
					}
					if(event instanceof CallEvent)
						item.setText(trigger.getLabel()+" -> "+event.getLabel()+" -> "+com.cea.papyrus.umlutils.Operation.getSignature(((CallEvent)event).getOperation(), true));
					else if(event instanceof SignalEvent)
						item.setText(trigger.getLabel()+" -> "+event.getLabel()+" -> "+((SignalEvent)event).getSignal().getLabel());
					else if(event instanceof ChangeEvent){
						ChangeEvent changeEvent = (ChangeEvent)event;
						if((changeEvent.getChangeExpression() != null) && (changeEvent.getChangeExpression() instanceof LiteralString))
							item.setText(trigger.getLabel()+" -> "+event.getLabel()+" -> when "+changeEvent.getChangeExpression().stringValue());
						else
							item.setText(trigger.getLabel()+" -> "+event.getLabel());
					}
					else if(event instanceof TimeEvent){
						TimeEvent timeEvent = (TimeEvent)event;
						if((timeEvent.getWhen() != null) && (timeEvent.getWhen() instanceof LiteralString))
							item.setText(trigger.getLabel()+" -> "+event.getLabel()+" -> "+timeEvent.getWhen().stringValue());
						else
							item.setText(trigger.getLabel()+" -> "+event.getLabel());
					}
					else if(event instanceof AnyReceiveEvent)
						item.setText(trigger.getLabel()+" -> "+event.getLabel()+" -> all");
				}
				else{
					item.setImage(PapyrusPlugin.getImage(TRIGGER_IMAGE));
					item.setText(trigger.getLabel());
				}
			}
			if(table.getItemCount() != 0){
				if(editPart != null){
					if(editPart instanceof TransitionEditPart){
						TransitionGuardAndAllDisplayEditPart guardAndAll = ((TransitionEditPart)editPart).getGuardAndAllDisplayEditPart();
						GraphNode gN = guardAndAll.getGraphNode();
						if((gN != null) && (gN.getProperty(EdgeTransitionHelper.TRANSITION_DISPLAY_TRIGGERS) != null))
							gN.getProperty(EdgeTransitionHelper.TRANSITION_DISPLAY_TRIGGERS).setValue(EdgeTransitionHelper.TRANSITION_DISPLAY);
					}
				}
			}
		}
	}

	/**
	 * Executed behavior when addButton pressed.
	 */
	public void addButtonPressed(){
		menu.setVisible(true);
	}

	/**
	 * Executed behavior when removeButton pressed.
	 */
	public void removeButtonPressed(){
		CompoundCommand command = new CompoundCommand();
		//get selection and delete it
		TableItem[] tableItems = table.getSelection();
		for(int i=0; i<tableItems.length; i++){
			//get trigger
			Trigger trigger = (Trigger)tableItems[i].getData(KEY);
			command.add(UMLDi2ElementDeleteFactory.eINSTANCE.createCommand(trigger));
		}

		//execute command
		CommandStack stack = CommandStackUtils.getCommandStack();
		Assert.isNotNull(stack, "Impossible to adapt current editor into a CommandStack");
		stack.execute(command);
	}

	/**
	 * Executed behavior when downButton pressed.
	 */
	public void downButtonPressed(){
		List triggers = null;

		Assert.isNotNull(element.getUml2Element(), "Passed argument is null");
		Assert.isTrue(((element.getUml2Element() instanceof State) || (element.getUml2Element() instanceof Transition)), "Passed argument is not a Transition nor a State");

		if(element.getUml2Element() instanceof Transition)
			triggers = ((Transition)element.getUml2Element()).getTriggers();
		else if(element.getUml2Element() instanceof State)
			triggers = ((State)element.getUml2Element()).getDeferrableTriggers();

		CompoundCommand compoundCommand = new CompoundCommand();
		TableItem[] tableItems = table.getSelection();

		for(int i=(tableItems.length-1); i>=0; i--){
			//get trigger
			Trigger trigger = (Trigger)tableItems[i].getData(KEY);
			int newIndex = table.indexOf(tableItems[i]) + 1;
			if(newIndex < triggers.size()){
				compoundCommand.add(new MoveInListCommand(triggers, trigger, newIndex));
			}
		}
		//execute command
		CommandStack stack = CommandStackUtils.getCommandStack();
		Assert.isNotNull(stack, "Impossible to adapt current editor into a CommandStack");
		stack.execute(compoundCommand);
	}

	/**
	 * Executed behavior when upButton pressed.
	 */
	public void upButtonPressed(){
		List triggers = null;

		Assert.isNotNull(element.getUml2Element(), "Passed argument is null");
		Assert.isTrue(((element.getUml2Element() instanceof State) || (element.getUml2Element() instanceof Transition)), "Passed argument is not a Transition nor a State");

		if(element.getUml2Element() instanceof Transition)
			triggers = ((Transition)element.getUml2Element()).getTriggers();
		else if(element.getUml2Element() instanceof State)
			triggers = ((State)element.getUml2Element()).getDeferrableTriggers();

		CompoundCommand compoundCommand = new CompoundCommand();
		TableItem[] tableItems = table.getSelection();
		for(int i=0; i<tableItems.length; i++){
			//get trigger
			Trigger trigger = (Trigger)tableItems[i].getData(KEY);
			int newIndex = table.indexOf(tableItems[i]) - 1;
			if(newIndex >= 0){
				compoundCommand.add(new MoveInListCommand(triggers, trigger, newIndex));
			}
		}
		//execute command
		CommandStack stack = CommandStackUtils.getCommandStack();
		Assert.isNotNull(stack, "Impossible to adapt current editor into a CommandStack");
		stack.execute(compoundCommand);
	}

	/**
	 * Loads a new property page on the effect
	 * 
	 * @param item
	 */
	public void editItem(TableItem item){
		//TODO: find a solution to load a new property page

		Trigger trigger = (Trigger)item.getData(KEY);
		if(trigger != null){
			EditPart selection = null;

			Iterator<EditPart> i = LookForElement.getAllOutlineEditParts().iterator();
			while((selection == null) && i.hasNext()){
				EditPart current = i.next();
				if((current.getModel() != null) && current.getModel().equals(trigger))
					selection = current ;
			}
			if(selection != null){
				tabbedPropertySheetPage.selectionChanged(getActiveEditor(), new StructuredSelection(selection));
			}
		}
	}

	/**
	 * Disposes of the composite
	 */
	public void dispose(){
		super.dispose();

		if((anyReceiveEventItem != null) && !anyReceiveEventItem.isDisposed())
			anyReceiveEventItem.removeSelectionListener(anyReceiveEventItemListener);
		if((callEventItem != null) && !callEventItem.isDisposed())
			callEventItem.removeSelectionListener(callEventItemListener);
		if((changeEventItem != null) && !changeEventItem.isDisposed())
			changeEventItem.removeSelectionListener(changeEventItemListener);
		if((signalEventItem != null) && !signalEventItem.isDisposed())
			signalEventItem.removeSelectionListener(signalEventItemListener);
		if((timeEventItem != null) && !timeEventItem.isDisposed())
			timeEventItem.removeSelectionListener(timeEventItemListener);
	}

	/**
	 * Listener for the menu items.
	 */
	public class AddElementSelectionListener implements SelectionListener{

		/**
		 * Constructor with given class.
		 * 
		 * @param elementClass Class
		 */
		public AddElementSelectionListener(Class elementClass){
			this.elementClass = elementClass;
		}

		/**
		 * Stored Class.
		 */
		public Class elementClass;

		/**
		 * Does nothing.
		 * 
		 * @param e SelectionEvent.
		 */
		public void widgetDefaultSelected(SelectionEvent e) {
		}

		/**
		 * What to do when item selected depending on the type of Event we want to associate a Trigger to.
		 * 
		 * @param e SelectionEvent.
		 */
		public void widgetSelected(SelectionEvent e){
			Debug.debug(this, "create a Trigger associated to a "+elementClass.getSimpleName(), channel);

			//a compound command to do the job
			CompoundCommand compoundCommand = new CompoundCommand();

			Assert.isNotNull(element.getUml2Element(), "Passed argument is null");
			Assert.isTrue(((element.getUml2Element() instanceof State) || (element.getUml2Element() instanceof Transition)), "Passed argument is not a Transition nor a State");

			BehavioredClassifier behavioredClassifier = null;
			if(element.getUml2Element() instanceof Transition)
				behavioredClassifier = (BehavioredClassifier)((Transition)element.getUml2Element()).containingStateMachine().getOwner();
			else if(element.getUml2Element() instanceof State)
				behavioredClassifier = (BehavioredClassifier)((State)element.getUml2Element()).containingStateMachine().getOwner();

			//case of a CallEvent
			if(elementClass.equals(CallEvent.class)){
				//we want to select an Operation from the BehavioredClassifier associated to the element (Transition or State)
				//open a dialog for this
				BehavioredClassifierOperationTreeSelectionDialog dialog = new BehavioredClassifierOperationTreeSelectionDialog(parent.getShell(), behavioredClassifier);
				int result = dialog.open();

				//check the result
				if(result == IDialogConstants.OK_ID){
					//declared final so as to be used in inner classes
					final Operation operation = dialog.getResult(); 
					if(operation != null){
						//an operation was chosen

						//we need to create a new Trigger
						final Trigger trigger = UMLFactory.eINSTANCE.createTrigger();
						trigger.setName(new NodeTriggerHelper().getNewUMLElementName(element.getUml2Element()));

						//we need to check whether there exists already a CallEvent associated to the chosen Operation in the nearest Package
						//if not we need to create one
						org.eclipse.uml2.uml.Package thePackage = behavioredClassifier.getNearestPackage();
						Iterator iter = thePackage.allOwnedElements().iterator();
						boolean found = false;
						CallEvent callEvent = null;
						while(!found && iter.hasNext()){
							org.eclipse.uml2.uml.Element element = (org.eclipse.uml2.uml.Element)iter.next();
							if((element instanceof CallEvent) && operation.equals(((CallEvent)element).getOperation())){
								found = true;
								callEvent = (CallEvent)element;
							}
						}
						if(!found){
							//we need to create a CallEvent associated to this Operation
							final UMLElementCreateCommand callEventCreateCommand = new UMLElementCreateCommand(CallEvent.class, thePackage);
							compoundCommand.add(callEventCreateCommand);
							//another command to associate the CallEvent to the Operation
							Command associateCallEventToOperationCommand = new Command("associateCallEventToOperationCommand"){
								private CallEvent event = null;

								@Override
								public void execute(){
									//get the previously created CallEvent from command
									event = (CallEvent)callEventCreateCommand.getElement();
									if(event != null){
										event.setOperation(operation);
									}
									else{
										Debug.error(this, "associateCallEventToOperationCommand: event is null", channel);
									}
								}

								@Override
								public boolean canUndo(){
									return (event != null);
								}

								@Override
								public void undo(){
									if(event != null){
										event.setOperation(null);
									}
									else{
										Debug.error(this, "associateCallEventToOperationCommand: event is null", channel);
									}
								}

								@Override
								public void redo(){
									execute();
								}
							};
							compoundCommand.add(associateCallEventToOperationCommand);

							//then a command to associate the Trigger to the CallEvent
							Command associateTriggerToCallEventCommand = new Command("associateTriggerToCallEventCommand"){
								private CallEvent event = null;

								@Override
								public void execute(){
									//get the previously created CallEvent from command
									event = (CallEvent)callEventCreateCommand.getElement();
									if((event != null) && (trigger != null)){
										trigger.setEvent(event);
									}
									else{
										Debug.error(this, "associateTriggerToCallEventCommand: event or trigger is null", channel);
									}
								}

								@Override
								public boolean canUndo(){
									return ((event != null) && (trigger!= null));
								}

								@Override
								public void undo(){
									if((event != null) && (trigger != null)){
										trigger.setEvent(null);
									}
									else{
										Debug.error(this, "associateTriggerToCallEventCommand: event or trigger is null", channel);
									}
								}

								@Override
								public void redo(){
									execute();
								}
							};
							compoundCommand.add(associateTriggerToCallEventCommand);

							final org.eclipse.uml2.uml.Element elt = element.getUml2Element(); 
							//finally we set the link between Trigger and Element (Transition or State)
							Command associateTriggerToElementCommand = new Command("associateTriggerToElementCommand"){

								@Override
								public void execute(){
									if((elt != null) && (trigger != null)){
										//test the Element
										if(elt instanceof Transition)
											((Transition)elt).getTriggers().add(trigger);
										else if(elt instanceof State)
											((State)elt).getDeferrableTriggers().add(trigger);
									}
									else{
										Debug.error(this, "associateTriggerToElementCommand: element or trigger is null", channel);
									}
								}

								@Override
								public boolean canUndo(){
									return ((elt != null) && (trigger!= null));
								}

								@Override
								public void undo(){
									if((elt != null) && (trigger != null)){
										if(elt instanceof Transition)
											((Transition)elt).getTriggers().remove(trigger);
										else if(elt instanceof State)
											((State)elt).getDeferrableTriggers().remove(trigger);
									}
									else{
										Debug.error(this, "associateTriggerToElementCommand: element or trigger is null", channel);
									}
								}

								@Override
								public void redo(){
									execute();
								}
							};
							compoundCommand.add(associateTriggerToElementCommand);
						}
						else{
							//there already exists a CallEvent for the chosen operation
							//we need to associate the Trigger to it
							//this time the CallEvent is directly read from the container Class (declared final to this purpose)
							final CallEvent event = callEvent;
							Command associateTriggerToCallEventCommand = new Command("associateTriggerToCallEventCommand"){

								@Override
								public void execute(){
									if((event != null) && (trigger != null)){
										trigger.setEvent(event);
									}
									else{
										Debug.error(this, "associateTriggerToCallEventCommand: event or trigger is null", channel);
									}
								}

								@Override
								public boolean canUndo(){
									return ((event != null) && (trigger!= null));
								}

								@Override
								public void undo(){
									if((event != null) && (trigger != null)){
										trigger.setEvent(null);
									}
									else{
										Debug.error(this, "associateTriggerToCallEventCommand: event or trigger is null", channel);
									}
								}

								@Override
								public void redo(){
									execute();
								}
							};
							compoundCommand.add(associateTriggerToCallEventCommand);

							//finally we set the link between Trigger and Element
							final org.eclipse.uml2.uml.Element elt = element.getUml2Element(); 
							Command associateTriggerToElementCommand = new Command("associateTriggerToElementCommand"){

								@Override
								public void execute(){
									if((elt != null) && (trigger != null)){
										//test the Element
										if(elt instanceof Transition)
											((Transition)elt).getTriggers().add(trigger);
										else if(elt instanceof State)
											((State)elt).getDeferrableTriggers().add(trigger);
									}
									else{
										Debug.error(this, "associateTriggerToElementCommand: element or trigger is null", channel);
									}
								}

								@Override
								public boolean canUndo(){
									return ((element != null) && (trigger!= null));
								}

								@Override
								public void undo(){
									if((element != null) && (trigger != null)){
										if(elt instanceof Transition)
											((Transition)elt).getTriggers().remove(trigger);
										else if(elt instanceof State)
											((State)elt).getDeferrableTriggers().remove(trigger);
									}
									else{
										Debug.error(this, "associateTriggerToElementCommand: element or trigger is null", channel);
									}
								}

								@Override
								public void redo(){
									execute();
								}
							};
							compoundCommand.add(associateTriggerToElementCommand);}
					}
				}
			}
			//case of a SignalEvent
			else if(elementClass.equals(SignalEvent.class)){
				//we want to select a Signal from the Reception list of the BehavioredClassifier associated to the Element (Transition or State)

				Assert.isTrue(behavioredClassifier instanceof org.eclipse.uml2.uml.Class, "The associated BehavioredClassifier is not a Class");
				//open a dialog for this
				ActiveClassSignalTreeSelectionDialog dialog = new ActiveClassSignalTreeSelectionDialog(parent.getShell(), (org.eclipse.uml2.uml.Class)behavioredClassifier);
				int result = dialog.open();

				//check the result
				if(result == IDialogConstants.OK_ID){
					//declared final so as to be used in inner classes
					final Signal signal = dialog.getResult();
					if(signal != null){
						//a signal was chosen

						//we need to create a new Trigger
						final Trigger trigger = UMLFactory.eINSTANCE.createTrigger();
						trigger.setName(new NodeTriggerHelper().getNewUMLElementName(element.getUml2Element()));

						//we need to check whether there exists already a SignalEvent associated to the chosen Signal in the nearest Package
						//if not we need to create one
						org.eclipse.uml2.uml.Package thePackage = behavioredClassifier.getNearestPackage();
						Iterator iter = thePackage.allOwnedElements().iterator();
						boolean found = false;
						SignalEvent signalEvent = null;
						while(!found && iter.hasNext()){
							org.eclipse.uml2.uml.Element element = (org.eclipse.uml2.uml.Element)iter.next();
							if((element instanceof SignalEvent) && signal.equals(((SignalEvent)element).getSignal())){
								found = true;
								signalEvent = (SignalEvent)element;
							}
						}
						if(!found){
							//we need to create a SignalEvent associated to this Signal
							final UMLElementCreateCommand signalEventCreateCommand = new UMLElementCreateCommand(SignalEvent.class, thePackage);
							compoundCommand.add(signalEventCreateCommand);
							//another command to associate the SignalEvent to the Signal
							Command associateSignalEventToSignalCommand = new Command("associateSignalEventToSignalCommand"){
								private SignalEvent event = null;

								@Override
								public void execute(){
									//get the previously created SignalEvent from command
									event = (SignalEvent)signalEventCreateCommand.getElement();
									if(event != null){
										event.setSignal(signal);
									}
									else{
										Debug.error(this, "associateSignalEventToSignalCommand: event is null", channel);
									}
								}

								@Override
								public boolean canUndo(){
									return (event != null);
								}

								@Override
								public void undo(){
									if(event != null){
										event.setSignal(null);
									}
									else{
										Debug.error(this, "associateSignalEventToSignalCommand: event is null", channel);
									}
								}

								@Override
								public void redo(){
									execute();
								}
							};
							compoundCommand.add(associateSignalEventToSignalCommand);

							//then a command to associate the Trigger to the SignalEvent
							Command associateTriggerToSignalEventCommand = new Command("associateTriggerToSignalEventCommand"){
								private SignalEvent event = null;

								@Override
								public void execute(){
									//get the previously created SignalEvent from command
									event = (SignalEvent)signalEventCreateCommand.getElement();
									if((event != null) && (trigger != null)){
										trigger.setEvent(event);
									}
									else{
										Debug.error(this, "associateTriggerToSignalEventCommand: event or trigger is null", channel);
									}
								}

								@Override
								public boolean canUndo(){
									return ((event != null) && (trigger!= null));
								}

								@Override
								public void undo(){
									if((event != null) && (trigger != null)){
										trigger.setEvent(null);
									}
									else{
										Debug.error(this, "associateTriggerToSignalEventCommand: event or trigger is null", channel);
									}
								}

								@Override
								public void redo(){
									execute();
								}
							};
							compoundCommand.add(associateTriggerToSignalEventCommand);

							//finally we set the link between Trigger and Element
							final org.eclipse.uml2.uml.Element elt = element.getUml2Element(); 							
							Command associateTriggerToElementCommand = new Command("associateTriggerToElementCommand"){

								@Override
								public void execute(){
									if((elt != null) && (trigger != null)){
										//test the Element
										if(elt instanceof Transition)
											((Transition)elt).getTriggers().add(trigger);
										else if(elt instanceof State)
											((State)elt).getDeferrableTriggers().add(trigger);
									}
									else{
										Debug.error(this, "associateTriggerToElementCommand: element or trigger is null", channel);
									}
								}

								@Override
								public boolean canUndo(){
									return ((elt != null) && (trigger!= null));
								}

								@Override
								public void undo(){
									if((elt != null) && (trigger != null)){
										if(elt instanceof Transition)
											((Transition)elt).getTriggers().remove(trigger);
										else if(elt instanceof State)
											((State)elt).getDeferrableTriggers().remove(trigger);
									}
									else{
										Debug.error(this, "associateTriggerToElementCommand: element or trigger is null", channel);
									}
								}

								@Override
								public void redo(){
									execute();
								}
							};
							compoundCommand.add(associateTriggerToElementCommand);
						}
						else{
							//there already exists a SignalEvent for the chosen Signal
							//we need to associate the Trigger to it
							//this time the CallEvent is directly read from the container Class (declared final to this purpose)
							final SignalEvent event = signalEvent;
							Command associateTriggerToSignalEventCommand = new Command("associateTriggerToSignalEventCommand"){

								@Override
								public void execute(){
									if((event != null) && (trigger != null)){
										trigger.setEvent(event);
									}
									else{
										Debug.error(this, "associateTriggerToSignalEventCommand: event or trigger is null", channel);
									}
								}

								@Override
								public boolean canUndo(){
									return ((event != null) && (trigger!= null));
								}

								@Override
								public void undo(){
									if((event != null) && (trigger != null)){
										trigger.setEvent(null);
									}
									else{
										Debug.error(this, "associateTriggerToSignalEventCommand: event or trigger is null", channel);
									}
								}

								@Override
								public void redo(){
									execute();
								}
							};
							compoundCommand.add(associateTriggerToSignalEventCommand);

							//finally we set the link between Trigger and Element
							final org.eclipse.uml2.uml.Element elt = element.getUml2Element(); 
							Command associateTriggerToElementCommand = new Command("associateTriggerToElementCommand"){

								@Override
								public void execute(){
									if((elt != null) && (trigger != null)){
										//test the Element
										if(elt instanceof Transition)
											((Transition)elt).getTriggers().add(trigger);
										else if(elt instanceof State)
											((State)elt).getDeferrableTriggers().add(trigger);
									}
									else{
										Debug.error(this, "associateTriggerToElementCommand: element or trigger is null", channel);
									}
								}

								@Override
								public boolean canUndo(){
									return ((elt != null) && (trigger!= null));
								}

								@Override
								public void undo(){
									if((elt != null) && (trigger != null)){
										if(elt instanceof Transition)
											((Transition)elt).getTriggers().remove(trigger);
										else if(elt instanceof State)
											((State)elt).getDeferrableTriggers().remove(trigger);
									}
									else{
										Debug.error(this, "associateTriggerToElementCommand: element or trigger is null", channel);
									}
								}

								@Override
								public void redo(){
									execute();
								}
							};
							compoundCommand.add(associateTriggerToElementCommand);
						}
					}
				}
			}
			//case of an AnyReceiveEvent
			else if(elementClass.equals(AnyReceiveEvent.class)){
				org.eclipse.uml2.uml.Package thePackage = behavioredClassifier.getNearestPackage();
				PackageAnyReceiveEventTreeSelectionDialog dialog = new PackageAnyReceiveEventTreeSelectionDialog(parent.getShell(), thePackage);
				int result = dialog.open();

				//check the result
				if(result == IDialogConstants.OK_ID){
					//declared final so as to be used in inner classes
					final AnyReceiveEvent anyReceiveEvent = dialog.getResult(); 
					if(anyReceiveEvent != null){
						//we need to create a new Trigger
						final Trigger trigger = UMLFactory.eINSTANCE.createTrigger();
						trigger.setName(new NodeTriggerHelper().getNewUMLElementName(element.getUml2Element()));

						Command associateTriggerToEventCommand = null;

						associateTriggerToEventCommand = new Command("associateTriggerToEventCommand"){
							@Override
							public void execute(){
								if((anyReceiveEvent != null) && (trigger != null)){
									trigger.setEvent(anyReceiveEvent);
								}
								else{
									Debug.error(this, "associateTriggerToEventCommand: event or trigger is null", channel);
								}
							}

							@Override
							public boolean canUndo(){
								return ((anyReceiveEvent != null) && (trigger!= null));
							}

							@Override
							public void undo(){
								if((anyReceiveEvent != null) && (trigger != null)){
									trigger.setEvent(null);
								}
								else{
									Debug.error(this, "associateTriggerToEventCommand: event or trigger is null", channel);
								}
							}

							@Override
							public void redo(){
								execute();
							}
						};
						compoundCommand.add(associateTriggerToEventCommand);
						//finally we set the link between Trigger and Element
						final org.eclipse.uml2.uml.Element elt = element.getUml2Element(); 
						Command associateTriggerToElementCommand = new Command("associateTriggerToElementCommand"){

							@Override
							public void execute(){
								if((elt != null) && (trigger != null)){
									//test the Element
									if(elt instanceof Transition)
										((Transition)elt).getTriggers().add(trigger);
									else if(elt instanceof State)
										((State)elt).getDeferrableTriggers().add(trigger);
								}
								else{
									Debug.error(this, "associateTriggerToElementCommand: element or trigger is null", channel);
								}
							}

							@Override
							public boolean canUndo(){
								return ((elt != null) && (trigger!= null));
							}

							@Override
							public void undo(){
								if((elt != null) && (trigger != null)){
									if(elt instanceof Transition)
										((Transition)elt).getTriggers().remove(trigger);
									else if(elt instanceof State)
										((State)elt).getDeferrableTriggers().remove(trigger);
								}
								else{
									Debug.error(this, "associateTriggerToElementCommand: element or trigger is null", channel);
								}
							}

							@Override
							public void redo(){
								execute();
							}
						};
						compoundCommand.add(associateTriggerToElementCommand);

					}
				}
			}
			//case of a ChangeEvent
			else if(elementClass.equals(ChangeEvent.class)){
				org.eclipse.uml2.uml.Package thePackage = behavioredClassifier.getNearestPackage();
				PackageChangeEventTreeSelectionDialog dialog = new PackageChangeEventTreeSelectionDialog(parent.getShell(), thePackage);
				int result = dialog.open();

				//check the result
				if(result == IDialogConstants.OK_ID){
					//declared final so as to be used in inner classes
					final ChangeEvent changeEvent = dialog.getResult(); 
					if(changeEvent != null){
						//we need to create a new Trigger
						final Trigger trigger = UMLFactory.eINSTANCE.createTrigger();
						trigger.setName(new NodeTriggerHelper().getNewUMLElementName(element.getUml2Element()));

						Command associateTriggerToEventCommand = null;

						associateTriggerToEventCommand = new Command("associateTriggerToEventCommand"){
							@Override
							public void execute(){
								if((changeEvent != null) && (trigger != null)){
									trigger.setEvent(changeEvent);
								}
								else{
									Debug.error(this, "associateTriggerToEventCommand: event or trigger is null", channel);
								}
							}

							@Override
							public boolean canUndo(){
								return ((changeEvent != null) && (trigger!= null));
							}

							@Override
							public void undo(){
								if((changeEvent != null) && (trigger != null)){
									trigger.setEvent(null);
								}
								else{
									Debug.error(this, "associateTriggerToEventCommand: event or trigger is null", channel);
								}
							}

							@Override
							public void redo(){
								execute();
							}
						};
						compoundCommand.add(associateTriggerToEventCommand);
						//finally we set the link between Trigger and Element
						final org.eclipse.uml2.uml.Element elt = element.getUml2Element(); 
						Command associateTriggerToElementCommand = new Command("associateTriggerToElementCommand"){

							@Override
							public void execute(){
								if((elt != null) && (trigger != null)){
									//test the Element
									if(elt instanceof Transition)
										((Transition)elt).getTriggers().add(trigger);
									else if(elt instanceof State)
										((State)elt).getDeferrableTriggers().add(trigger);
								}
								else{
									Debug.error(this, "associateTriggerToElementCommand: element or trigger is null", channel);
								}
							}

							@Override
							public boolean canUndo(){
								return ((elt != null) && (trigger!= null));
							}

							@Override
							public void undo(){
								if((elt != null) && (trigger != null)){
									if(elt instanceof Transition)
										((Transition)elt).getTriggers().remove(trigger);
									else if(elt instanceof State)
										((State)elt).getDeferrableTriggers().remove(trigger);
								}
								else{
									Debug.error(this, "associateTriggerToElementCommand: element or trigger is null", channel);
								}
							}

							@Override
							public void redo(){
								execute();
							}
						};
						compoundCommand.add(associateTriggerToElementCommand);

					}
				}
			}
			//case of a TimeEvent
			else if(elementClass.equals(TimeEvent.class)){
				org.eclipse.uml2.uml.Package thePackage = behavioredClassifier.getNearestPackage();
				PackageTimeEventTreeSelectionDialog dialog = new PackageTimeEventTreeSelectionDialog(parent.getShell(), thePackage);
				int result = dialog.open();

				//check the result
				if(result == IDialogConstants.OK_ID){
					//declared final so as to be used in inner classes
					final TimeEvent timeEvent = dialog.getResult(); 
					if(timeEvent != null){
						//we need to create a new Trigger
						final Trigger trigger = UMLFactory.eINSTANCE.createTrigger();
						trigger.setName(new NodeTriggerHelper().getNewUMLElementName(element.getUml2Element()));

						Command associateTriggerToEventCommand = null;

						associateTriggerToEventCommand = new Command("associateTriggerToEventCommand"){
							@Override
							public void execute(){
								if((timeEvent != null) && (trigger != null)){
									trigger.setEvent(timeEvent);
								}
								else{
									Debug.error(this, "associateTriggerToEventCommand: event or trigger is null", channel);
								}
							}

							@Override
							public boolean canUndo(){
								return ((timeEvent != null) && (trigger!= null));
							}

							@Override
							public void undo(){
								if((timeEvent != null) && (trigger != null)){
									trigger.setEvent(null);
								}
								else{
									Debug.error(this, "associateTriggerToEventCommand: event or trigger is null", channel);
								}
							}

							@Override
							public void redo(){
								execute();
							}
						};
						compoundCommand.add(associateTriggerToEventCommand);
						//finally we set the link between Trigger and Element
						final org.eclipse.uml2.uml.Element elt = element.getUml2Element(); 
						Command associateTriggerToElementCommand = new Command("associateTriggerToElementCommand"){

							@Override
							public void execute(){
								if((elt != null) && (trigger != null)){
									//test the Element
									if(elt instanceof Transition)
										((Transition)elt).getTriggers().add(trigger);
									else if(elt instanceof State)
										((State)elt).getDeferrableTriggers().add(trigger);
								}
								else{
									Debug.error(this, "associateTriggerToElementCommand: element or trigger is null", channel);
								}
							}

							@Override
							public boolean canUndo(){
								return ((elt != null) && (trigger!= null));
							}

							@Override
							public void undo(){
								if((elt != null) && (trigger != null)){
									if(elt instanceof Transition)
										((Transition)elt).getTriggers().remove(trigger);
									else if(elt instanceof State)
										((State)elt).getDeferrableTriggers().remove(trigger);
								}
								else{
									Debug.error(this, "associateTriggerToElementCommand: element or trigger is null", channel);
								}
							}

							@Override
							public void redo(){
								execute();
							}
						};
						compoundCommand.add(associateTriggerToElementCommand);

					}
				}
			}

			//execute compound command
			CommandStack stack = CommandStackUtils.getCommandStack();
			Assert.isNotNull(stack, "Impossible to adapt current editor into a CommandStackUtils");
			stack.execute(compoundCommand);

			//refresh contents
			refresh();
		}
	}

	private class MenuTooltipListener implements ArmListener, MenuListener {
		private DefaultToolTip tip;
		private Control control;

		public MenuTooltipListener(Control control) {
			this.control = control;
			tip = new DefaultToolTip(control, ToolTip.RECREATE, true);
		}

		public void widgetArmed(ArmEvent e) {
			MenuItem item = (MenuItem)e.widget;
			if(item.equals(anyReceiveEventItem))
				tip.setText("Create an anyReceiveEvent trigger");
			else if(item.equals(changeEventItem))
				tip.setText("Create new change trigger");
			else if(item.equals(timeEventItem))
				tip.setText("Create new time trigger");
			else if(item.equals(signalEventItem)){
				if(!item.isEnabled())
					tip.setText("Element is not an active class");
				else
					tip.setText("Create new trigger on signal");
			}
			else if(item.equals(callEventItem))
				tip.setText("Create new trigger on operation");
			tip.setPopupDelay(100);
			tip.setHideDelay(3000);
			Point p = control.toControl(control.getDisplay().getCursorLocation());
			p.x += 60;
			if(System.getProperty("os.name").startsWith("Mac OS X"))
				p.y -= 120;
			tip.show(p);
		}

		public void menuHidden(MenuEvent e) {
			tip.hide();
		}

		public void menuShown(MenuEvent e) {

		}
	}

}
